#pragma once

#include "Logger.hpp"
#include "Socket.hpp"
#include "InetAddr.hpp"
#include <iostream>
#include <sys/epoll.h>
#include <unistd.h>
#include <string>

static const int gsize = 64;

class EpollServer
{
public:
    EpollServer(uint16_t port)
        : _listensockfd(std::make_unique<TcpSocket>())
    {
        // 创建监听套接字
        _listensockfd->BuildListenSocket(port);
        // 创建epoll 句柄，调用epoll_create  int epoll_create(int size);
        _epfd = epoll_create(128); // epoll_create 的参数已经被忽略的，此处随便填就可以了
        if (_epfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建 epoll 模型失败";
            return;
        }

        // 走到此处就说明epoll 模型创建成功，可以debug 一下
        LOG(LogLevel::DEBUG) << "监听套接字： " << _listensockfd->Sockfd() << ", 创建epoll 模型成功: " << _epfd;
        // 需要将监听套接字加入epoll 中,使用epoll_ctl来添加
        //  int epoll_ctl(int epfd, int op, int fd,
        //               struct epoll_event *_Nullable event);
        // epoll 的第一个参数为epoll 所对应的文件描述符，第二个参数为操作，如果是添加就为EPOLL_CTL_ADD；第三个参数为所要关心的文件描述符
        // 第4个参数为所要关心文件描述符的事件；其中，在struct epoll_event 中需要设置fd 与 evevts
        struct epoll_event ev;
        // struct epoll_event
        // {
        //     uint32_t events;   /* Epoll events */
        //     epoll_data_t data; /* User data variable */
        // } __EPOLL_PACKED;
        ev.data.fd = _listensockfd->Sockfd();
        ev.events = EPOLLIN | EPOLLET; // 此处关心读事件
        int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, _listensockfd->Sockfd(), &ev);
        (void)n;
    }
    ~EpollServer() {}

    void Accepter()
    {
        InetAddr client;
        int sockfd = _listensockfd->Accept(&client);
        if (sockfd > 0)
        {
            // 获取连接成功
            // 将这个文件描述符也加入epoll 中，关心读事件
            epoll_event ev;
            ev.data.fd = sockfd;
            // 如果想要其工作模式为ET，在events 中添加EPOLLET;默认为LT模式
            //  ev.events = EPOLLIN;
            ev.events = EPOLLIN | EPOLLET;
            int n = epoll_ctl(_epfd, EPOLL_CTL_ADD, sockfd, &ev);
            (void)n;
            LOG(LogLevel::INFO) << "添加新连接到 epoll 中";
        }
    }

    void Recver(int sockfd)
    {
        // 从sockfd 中读取接受数据，此处的写法存在bug , 暂时这样处理
        char buffer[1024];
        ssize_t n = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (n == 0)
        {
            // 表示对端关闭
            LOG(LogLevel::DEBUG) << "对端关闭， me too， sockfd:" << sockfd;
            // 先将此文件描述符从epoll 中移除，然后再关闭这个文件描述符
            int m = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr); // epoll_ctl 成功返回0，失败返回-1
            if (m < 0)
            {
                LOG(LogLevel::WARNING) << "移除文件描述符：" << sockfd << "失败";
            }
            close(sockfd);
        }
        else if (n < 0)
        {
            // 操作失败
            LOG(LogLevel::DEBUG) << "recv 读取失败， sockfd: " << sockfd;
            // 先将此文件描述符从epoll 中移除，然后再关闭这个文件描述符
            int m = epoll_ctl(_epfd, EPOLL_CTL_DEL, sockfd, nullptr); // epoll_ctl 成功返回0，失败返回-1
            if (m < 0)
            {
                LOG(LogLevel::WARNING) << "移除文件描述符：" << sockfd << "失败";
            }
            close(sockfd);
        }
        else
        {
            // 读取成功
            // 构建应答然后send
            buffer[n] = 0;
            std::cout << "client say#" << buffer;
            std::string send_str = "server say#";
            send_str += buffer;

            send(sockfd, send_str.c_str(), send_str.size(), 0);
        }
    }

    void Dispatcher(int num)
    {
        // 判断哪些文件描述符就绪了，然后判断这个文件描述符关心的事件是什么，然后做出对应的操作
        for (int i = 0; i < num; i++)
        {
            int fd = _revs[i].data.fd;
            uint32_t events = _revs[i].events;

            // 关心的读事件有哪些文件描述符就绪了
            if (events & EPOLLIN)
            {
                // 读事件就绪，判断是什么套接字
                if (fd == _listensockfd->Sockfd())
                {
                    Accepter();
                }
                else
                {
                    Recver(fd);
                }
            }
            // 关心写
            //    if(events & EPOLLOUT)
            //    {}
        }
    }

    void Start()
    {
        int timeout = 1000; // 和select poll 的参数一样
        while (true)
        {
            // 调用epoll_wait 来进行等待，有事件就绪了就回调出去处理事件
            // int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
            // 第一个参数为epoll 模型所对应的文件描述符，第二个参数为存放就绪事件的数组，可以设置成成员变量，第三个参数为此数组的大小
            // 无需担心我们设置的数组大小不够用；就绪队列中的任务能取多少处理就取多少任务处理，没有处理完的，epoll_wait 会再次提醒去处理；第4个参数为等待时间
            // epoll_wait 的返回值与select poll 的返回值是一个意思；返回0，表示超时了没有事件就绪；-1，表示出错；大于0 表示有n 个事件就绪；
            int n = epoll_wait(_epfd, _revs, gsize, timeout);
            switch (n)
            {
            case 0:
                LOG(LogLevel::DEBUG) << "timeout……";
                break;
            case -1:
                LOG(LogLevel::DEBUG) << "epoll error";
                break;
            default:
                // 有就绪事件到来
                Dispatcher(n); // n为_revs 中有效数据的个数，传进去方便遍历
                break;
            }
        }
    }

private:
    // 监听套接字
    std::unique_ptr<Socket> _listensockfd;
    int _epfd;
    struct epoll_event _revs[gsize];
};